﻿#region MIT
/*
Engine Of Evermore (http://code.google.com/p/engineofevermore/)
Copyright (c) 2008 Michael Woerister

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using WeifenLuo.WinFormsUI.Docking;
using System.IO;
using System.Diagnostics;
using Blue.ResourceManager;

namespace EngineOfEvermore.Editor.Common
{
    public partial class ResourceExplorer : UserControl
    {
        const String FOLDER_CLOSED_IMAGE_KEY = "FolderClosedIcon";
        const String FOLDER_OPENED_IMAGE_KEY = "FolderOpenedIcon";
        const String DEFAULT_RESOURCE_IMAGE_KEY = "DefaultFileIcon";

        #region Variables
        IStatusMessageConsumer _statusMessageConsumer;
        bool _editingNewDirectoryNode;
        bool _editingNewFileNode;
        String _newFileNodeName;
        String _newDirectoryNodeName;
        String[] _currentlyRenamedResource;
        String[] _currentlyRenamedDirectory;

        String _currentlyEditedResourceExtension;
        
        TreeNode _rootNode;
        HashSet<String> _ignoredDirectories;
        String _rootDirectory = "";
        String _resourceExtensions = "*";
        ContextMenuStrip _resourceContextMenu;

        TreeNode _dropSelection;
        long _scrollTick;

        bool _disableRenaming;
        #endregion

        #region NodeData class
        public class NodeData
        {
            public NodeData( bool isDirectory )
            {
                IsDirectory = isDirectory;
            }

            public readonly bool IsDirectory;
        
        }
        #endregion

        #region TreeViewSorter class
        class TreeViewSorter : System.Collections.IComparer
        {


            #region IComparer Members

            public int Compare( object x, object y )
            {
                if ( !( x is TreeNode ) )
                {
                    throw new ArgumentException();
                }

                if ( !( y is TreeNode ) )
                {
                    throw new ArgumentException();
                }

                TreeNode n1 = (TreeNode) x;
                TreeNode n2 = (TreeNode) y;
                bool n1IsDir = ((NodeData)n1.Tag).IsDirectory;
                bool n2IsDir = ((NodeData)n2.Tag).IsDirectory;

                if ( n1IsDir == n2IsDir )
                {
                    return n1.Text.CompareTo( n2.Text );
                }

                if ( n1IsDir && !n2IsDir )
                {
                    return -1;
                }

                Debug.Assert( !n1IsDir && n2IsDir );

                return 1;
            }

            #endregion
        }
        #endregion

        #region Constructor
        public ResourceExplorer()
        {
            _ignoredDirectories = new HashSet<string>(
                new String[] {
                    ".svn", ".cvs"
                }
                );

            InitializeComponent();
            _treeView.TreeViewNodeSorter = new TreeViewSorter();
        }
        #endregion

        #region Events
        /// <summary>
        /// Called after the user has given a directoryName to a newly created resource.
        /// </summary>
        /// <remarks>
        /// If there is an error while creating the new resource then an exception can
        /// be thrown. This will cause the new node to be deleted and the exception
        /// message to be displayed by the IStatusMessageConsumer.
        /// </remarks>
        public event EventHandler<ResourceNameEventArgs> AfterNewResourceNamed;

        public event EventHandler<ResourceNameEventArgs> ResourceOpened;

        public event EventHandler<TryRenameResourceEventArgs> TryRenameResource;

        public event EventHandler<ResourceNameEventArgs> SelectionChanged;
        #endregion

        #region Public Methods
        public void BeginRenamingSelectedNode()
        {
            _treeView.SelectedNode.BeginEdit();
        }

        public void CreateNewResource( String resourceName )
        {
            resourceName = ResourceManager.NormalizeName( resourceName );
            String subDirs = Path.GetDirectoryName( resourceName );
            
            String[] parts = subDirs.Split( new char[] { Path.DirectorySeparatorChar } );

            TreeNode node = _rootNode;

            Debug.Assert( _rootNode.Text == parts[0] );

            for ( int i = 1; i < parts.Length; ++i )
            {
                Debug.Assert( node.Nodes.ContainsKey( parts[i] ) );
                node = node.Nodes[parts[i]];
            }

            Debug.Assert( ( (NodeData) node.Tag ).IsDirectory );

            TreeNode newNode = new TreeNode();
            newNode.Text = Path.GetFileName( resourceName );
            newNode.Name = newNode.Text;
            newNode.ImageKey = _getImageKey( Path.GetExtension( resourceName ) );
            newNode.SelectedImageKey = newNode.ImageKey;
            newNode.Tag = new NodeData( false );
            newNode.ContextMenuStrip = _resourceContextMenu;

            node.Nodes.Add( newNode );

            _treeView.SelectedNode = newNode;

            _editingNewFileNode = true;

            newNode.BeginEdit();
        }

        public void CreateNewResourceInSelectedDirectory( String localName )
        { 
            Debug.Assert( _treeView.SelectedNode != null );

            String resourceName = 
                Path.Combine( _treeView.SelectedNode.FullPath, localName );

            CreateNewResource( resourceName );
        }

        public void DeleteSelectedResource()
        {
            Debug.Assert( !_editingNewDirectoryNode );
            Debug.Assert( !_editingNewFileNode );
            Debug.Assert( !( (NodeData) _treeView.SelectedNode.Tag ).IsDirectory );

            DialogResult result = 
                MessageBox.Show( 
                    "Do you really want to delete this resource?", 
                    "Delete Resource", 
                    MessageBoxButtons.OKCancel, 
                    MessageBoxIcon.Warning, 
                    MessageBoxDefaultButton.Button2 
                    );

            String resourceName = SelectedResourceName;

            if ( result == DialogResult.OK )
            {
                bool succeeded = true;

                try
                {
                    ResourceManager.DeleteResource( resourceName );
                }
                catch ( ResourceManagerException ex )
                {
                    succeeded = false;
                    _showStatusMessage( ex.Message );
                }

                if ( succeeded )
                {
                    _showStatusMessage( "Deleted Resource " + resourceName + " ..." );
                }
            }
        }

        public void OpenSelectedResource()
        {
            if ( ResourceManager.ResourceDirectoryExists( SelectedResourceName ) )
                return;

            Debug.Assert( ResourceManager.ResourceExists( SelectedResourceName ) );

            if ( ResourceOpened != null )
            {
                ResourceOpened( this, new ResourceNameEventArgs( SelectedResourceName ) );
            }
        }

        #endregion

        #region Properties
        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "The Root Directory (relative to the Resource Directory) of the TreeView." )]
        public String RootDirectory
        {
            get { return _rootDirectory; }
            set 
            { 
                _rootDirectory = value;

                if ( String.IsNullOrEmpty( _rootDirectory ) )
                {
                    return;
                }

                if ( _rootDirectory[0] == Path.DirectorySeparatorChar ||
                     _rootDirectory[0] == Path.AltDirectorySeparatorChar )
                {
                    _rootDirectory = _rootDirectory.Substring( 1 );
                }

                if ( DesignMode )
                {
                    _createRoot();
                }
            }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "The list of images that can be used as TreeNode icons. This list is read-only." )]
        [ReadOnly( true )]
        public ImageList Images
        {
            get { return _treeViewImageList; }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "Contains the Images that are used by the TreeView." )]
        [ReadOnly( true )]
        public ContextMenuStrip DirectoryContextMenu
        {
            get { return _directoryContextMenu; }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "A list of file extensions (e.g.: png bmp gif)" )]
        public String ResourceExtensions
        {
            get { return _resourceExtensions; }
            set { _resourceExtensions = value; }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "A list ignored directory names (e.g.: .svn .cvs)")]
        public String IgnoredDirectories
        {
            get 
            {
                StringBuilder sb = new StringBuilder();

                List<String> list = _ignoredDirectories.ToList();
                list.Sort();

                foreach ( String s in list )
                {
                    sb.Append( s ).Append( ' ' );
                }

                return sb.ToString();
            }
            set 
            {
                String[] dirs = value.Split( new char[] { ' ', ';', ',' } );

                _ignoredDirectories.Clear();

                foreach ( String s in dirs.Where( x => x.Length > 0 ) )
                {
                    _ignoredDirectories.Add( s );
                }
            }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [Description( "A list ignored directory names (e.g.: .svn .cvs)" )]
        public ContextMenuStrip ResourceContextMenu
        {
            get { return _resourceContextMenu; }
            set { _resourceContextMenu = value; }
        }

        [Browsable( true )]
        [Category( "Resource Explorer" )]
        [DefaultValue( false )]
        [Description( "Allows for disabling renaming and dragging of nodes." )]
        public bool DisableRenaming
        {
            get 
            { 
                return _disableRenaming; 
            }
            set 
            {
                _disableRenaming = value;

                _treeView.AllowDrop = !value;
            }
        }

        [Browsable( false )]
        public IStatusMessageConsumer StatusMessageConsumer
        {
            get { return _statusMessageConsumer; }
            set { _statusMessageConsumer = value; }
        }

        [Browsable( false )]
        public String SelectedResourceName
        {
            get
            {
                if ( _treeView.SelectedNode != null )
                {
                    return _treeView.SelectedNode.FullPath;
                }
                else
                {
                    return String.Empty;
                }
            }
        }

        [Browsable( false )]
        public bool IsSelectedResourceDirectory
        {
            get 
            {
                if ( _treeView.SelectedNode != null )
                {
                    return ( (NodeData) _treeView.SelectedNode.Tag ).IsDirectory;
                }
                else
                {
                    return false;
                }
            }
        }
        #endregion

        #region Private EventHandlers
        void _resourceExplorer_Load( object sender, EventArgs e )
        {
            Debug.Assert( DesignMode || ResourceManager.ResourceDirectoryExists( _rootDirectory ) );

            _createRoot();

            if ( !DesignMode )
            {
                IEnumerable<String> extensions = _getResourceExtensions();

                if ( extensions.Any( x => x == "*" ) )
                {
                    Debug.Assert( extensions.Count() == 1 );
                }

                foreach ( String ext in extensions )
                {
                    if ( ext.Length != 0 )
                    {
                        _loadResources( ext );
                    }
                }
                
                _loadDirectories();

                _subscribeToResourceManagerEvents();
            }
        }

        void _treeView_MouseDown( object sender, MouseEventArgs e )
        {
            TreeNode node = _treeView.GetNodeAt( e.Location );
            if ( node != null )
            {
                _treeView.SelectedNode = node;
            }
        }

        void _createDirectoryToolStripMenuItem_Click( object sender, EventArgs e )
        {
            _treeView.SelectedNode.Expand();

            TreeNode newNode = new TreeNode();
            newNode.Text = "New Directory";    
            newNode.ImageKey = FOLDER_CLOSED_IMAGE_KEY;
            newNode.SelectedImageKey = FOLDER_OPENED_IMAGE_KEY;
            newNode.ContextMenuStrip = _directoryContextMenu;
            newNode.Tag = new NodeData( true );
            
            _treeView.SelectedNode.Nodes.Add( newNode );
            _treeView.SelectedNode = newNode;
            _editingNewDirectoryNode = true;
            newNode.BeginEdit();
        }

        void _treeView_AfterLabelEdit( object sender, NodeLabelEditEventArgs e )
        {
            if ( ( (NodeData) e.Node.Tag ).IsDirectory )
            {
                _afterEditDirectoryNode( e );
            }
            else
            {
                _afterEditFileNode( e );
            }
        }

        void _afterEditDirectoryNode( NodeLabelEditEventArgs e )
        {
            if ( _editingNewDirectoryNode )
            {
                try
                {
                    if ( e.Label != null && e.Label.Length == 0 )
                    {
                        throw new Exception( "Directory name cannot be empty" );
                    }

                    if ( e.Label != null )
                    {
                        e.Node.Text = e.Label;
                    }

                    if ( ResourceManager.ResourceDirectoryExists( e.Node.FullPath ) )
                    {
                        throw new Exception( "Directory with that name already exists" );
                    }

                    _newDirectoryNodeName = e.Node.FullPath;
                    ResourceManager.CreateResourceDirectory( e.Node.FullPath );
                    _newDirectoryNodeName = null;

                    e.Node.Name = e.Node.Text;
                }
                catch ( Exception ex )
                {
                    _showStatusMessage( ex.Message );

                    if ( _treeView.Focused )
                    {
                        e.Node.BeginEdit();
                    }
                    else
                    {
                        e.CancelEdit = true;
                        e.Node.Remove();
                    }

                    _newDirectoryNodeName = null;
                    return;
                }
                
                _showStatusMessage( "Created Directory " + e.Node.FullPath + " ..." );
            }
            else
            {
                // This is the case when the label directoryName was not changed
                if ( e.Label == null )
                    return;

                String oldLabel = e.Node.Text;
                String oldResourceDirName = e.Node.FullPath;
                e.Node.Text = e.Label;
                String newResourceDirName = e.Node.FullPath;

                bool failed = false;
                bool reEditOnFailure = true;

                try
                {
                    if ( e.Label.Length == 0 )
                    {
                        throw new Exception( "Directory name cannot be empty" );
                    }

                    if ( ResourceManager.ResourceDirectoryExists( newResourceDirName ) )
                    {
                        throw new Exception( "Directory with that name already exists" );
                    }

                    DialogResult result = DialogResult.OK;

                    if ( result == DialogResult.OK )
                    {
                        if ( TryRenameResource != null )
                        {
                            TryRenameResourceEventArgs args =
                                new TryRenameResourceEventArgs( oldResourceDirName,
                                    newResourceDirName, true );

                            TryRenameResource( this, args );

                            if ( args.Cancel )
                            {
                                reEditOnFailure = false;
                                throw new Exception( "Action cancelled by user" );
                            }
                        }

                        e.Node.Name = e.Node.Text;
                        _currentlyRenamedDirectory = new String[] { oldResourceDirName, 
                            newResourceDirName };
                        ResourceManager.RenameResourceDirectory( oldResourceDirName,
                            newResourceDirName );

                        
                    }
                    else
                    {
                        e.Node.Text = oldLabel;
                        e.Node.Name = e.Node.Text;
                        e.CancelEdit = true;
                        return;
                    }
                }
                catch ( Exception ex )
                {
                    failed = true;
                    e.Node.Text = oldLabel;
                    e.CancelEdit = true;

                    _showStatusMessage( ex.Message );

                    if ( reEditOnFailure )
                    {
                        e.Node.BeginEdit();
                    }
                }
                finally
                {
                    _currentlyRenamedDirectory = null;
                }

                if ( !failed )
                {
                    _showStatusMessage( "Renamed Directory from " + oldResourceDirName + 
                        " to " + newResourceDirName + " ..." );
                }
            }

            _editingNewDirectoryNode = false;
        }

        void _afterEditFileNode( NodeLabelEditEventArgs e )
        {
            if ( _editingNewFileNode )
            {
                try
                {
                    if ( e.Label != null && e.Label.Length == 0 )
                    {
                        throw new Exception( "ResourceName cannot be empty" );
                    }

                    if ( e.Label != null )
                    {
                        e.Node.Text = e.Label;
                    }

                    if ( !e.Node.Text.EndsWith( _currentlyEditedResourceExtension ) )
                    {
                        e.Node.Text += _currentlyEditedResourceExtension;
                    }

                    // This has to be done so the added extension is not reverted again
                    e.CancelEdit = true;

                    if ( ResourceManager.ResourceExists( e.Node.FullPath ) )
                    {
                        throw new Exception( "A Resource with that name already exists" );
                    }

                    try
                    {
                        Debug.Assert( AfterNewResourceNamed != null );
                        ResourceNameEventArgs args =
                            new ResourceNameEventArgs( e.Node.FullPath );

                        _newFileNodeName = args.ResourceName;
                        AfterNewResourceNamed( this, args );
                        _newFileNodeName = null;
                    }
                    catch ( Exception ex )
                    {
                        _showStatusMessage( ex.Message );
                        _editingNewFileNode = false;
                        _newFileNodeName = null;
                        e.Node.Remove();
                    }

                    e.Node.Name = e.Node.Text;
                }
                catch ( Exception ex )
                {
                    _showStatusMessage( ex.Message );

                    if ( _treeView.Focused )
                    {
                        e.Node.BeginEdit();
                    }
                    else
                    {
                        e.Node.Remove();
                        _editingNewFileNode = false;
                        _newFileNodeName = null;
                    }

                    return;
                }

                _editingNewFileNode = false;
                _newFileNodeName = null;
            }
            else
            {
                // This is the case when the label was not changed
                if ( e.Label == null )
                    return;

                String oldLabel = e.Node.Text;
                String oldResourceName = e.Node.FullPath;
                e.Node.Text = e.Label;
                String newResourceName = e.Node.FullPath;

                bool failed = false;
                bool reEditOnFailure = true;

                try
                {
                    if ( e.Label.Length == 0 )
                    {
                        throw new Exception( "ResourceName cannot be empty" );
                    }

                    if ( !e.Node.Text.EndsWith( _currentlyEditedResourceExtension ) )
                    {
                        e.Node.Text += _currentlyEditedResourceExtension;
                        newResourceName = e.Node.FullPath;
                    }

                    // This has to be done so the added extension is not reverted again
                    e.CancelEdit = true;

                    if ( ResourceManager.ResourceExists( newResourceName ) )
                    {
                        throw new Exception( "Resource with that name already exists" );
                    }

                    if ( TryRenameResource != null )
                    {
                        TryRenameResourceEventArgs args =
                            new TryRenameResourceEventArgs( oldResourceName,
                                newResourceName, false );

                        TryRenameResource( this, args );

                        if ( args.Cancel )
                        {
                            reEditOnFailure = false;
                            throw new Exception( "Action cancelled by user" );
                        }
                    }

                    _currentlyRenamedResource = new String[] { oldResourceName, newResourceName };
                    ResourceManager.RenameResource( oldResourceName, newResourceName );
                    e.Node.Name = e.Node.Text;
                }
                catch ( Exception ex )
                {
                    failed = true;
                    e.Node.Text = oldLabel;
                    e.CancelEdit = true;
                    _showStatusMessage( ex.Message );

                    if ( reEditOnFailure )
                    {
                        e.Node.BeginEdit();
                    }
                }
                finally
                {
                    _currentlyRenamedResource = null;
                }

                if ( !failed )
                {
                    _showStatusMessage( "Renamed Resource from " + oldResourceName +
                        " to " + newResourceName + " ..." );
                }
            }
        }

        void _deleteDirectoryToolStripMenuItem_Click( object sender, EventArgs e )
        {
            _deleteSelectedDirectory();
        }

        private void _deleteSelectedDirectory()
        {
            Debug.Assert( _treeView.SelectedNode != null );
            Debug.Assert( _selectedNodeIsDirectory() );

            DialogResult result =
                MessageBox.Show(
                    "Do you really want to delete this directory and all files it contains?",
                    "Delete Directory",
                    MessageBoxButtons.OKCancel,
                    MessageBoxIcon.Warning,
                    MessageBoxDefaultButton.Button2
                    );

            if ( result == DialogResult.OK )
            {
                String path = _treeView.SelectedNode.FullPath;

                bool failed = false;

                try
                {
                    ResourceManager.DeleteResourceDirectory( path );
                }
                catch ( ResourceManagerException ex )
                {
                    Debug.Assert( ex.ExceptionReason == ResourceManagerException.Reason.RESOURCES_STILL_REFERENCED );
                    failed = true;
                    _showStatusMessage( ex.Message );
                }

                if ( !failed )
                {
                    _showStatusMessage( "Deleted directory " +
                        _treeView.SelectedNode.FullPath + " ... " );
                }
            }
        }

        void _directoryContextMenu_Opening( object sender, CancelEventArgs e )
        {
            if ( _treeView.SelectedNode == _rootNode )
            {
                _deleteDirectoryToolStripMenuItem.Enabled = false;
                _renameToolStripMenuItem.Enabled = false;
            }
            else
            {
                _deleteDirectoryToolStripMenuItem.Enabled = true;
                _renameToolStripMenuItem.Enabled = true;
            }
        }

        void _renameToolStripMenuItem_Click( object sender, EventArgs e )
        {
            _treeView.SelectedNode.BeginEdit();
        }

        void _treeView_BeforeLabelEdit( object sender, NodeLabelEditEventArgs e )
        {
            if ( e.Node == _rootNode || _disableRenaming )
            {
                e.CancelEdit = true;
            }
            else
            {
                if ( !( (NodeData) e.Node.Tag ).IsDirectory )
                {
                    _currentlyEditedResourceExtension = Path.GetExtension( e.Node.Text );
                }
            }
        }

        void _treeView_NodeMouseDoubleClick( object sender, TreeNodeMouseClickEventArgs e )
        {
            if ( !( (NodeData) e.Node.Tag ).IsDirectory )
            {
                OpenSelectedResource();
            }
        }

        void _treeView_KeyUp( object sender, KeyEventArgs e )
        {
            if ( e.KeyCode == Keys.Delete )
            {
                if ( _treeView.SelectedNode != null )
                {
                    e.Handled = true;

                    if ( _selectedNodeIsDirectory() )
                    {
                        _deleteSelectedDirectory();
                    }
                    else
                    {
                        DeleteSelectedResource();
                    }
                }
            }
        }

        void _treeView_DragEnter( object sender, DragEventArgs e )
        {
            DragDropEffects realEffect = DragDropEffects.None;

            if ( e.AllowedEffect == DragDropEffects.Move )
            {
                if ( e.Data.GetDataPresent( typeof( TreeNode ) ) )
                {
                    TreeNode node = (TreeNode) e.Data.GetData( typeof( TreeNode ) );

                    if ( node.TreeView == this._treeView && !_disableRenaming )
                    {
                        realEffect = DragDropEffects.Move;
                    }
                }
            }

            e.Effect = realEffect;
        }

        void _treeView_ItemDrag( object sender, ItemDragEventArgs e )
        {
            if ( !ReferenceEquals( e.Item, _rootNode ) )
            {
                DoDragDrop( e.Item, DragDropEffects.Move );
            }
        }

        void _treeView_DragDrop( object sender, DragEventArgs e )
        {
            _dropSplitterPanel.Visible = false;

            if ( _dropSelection != null )
            {
                TreeNode draggedNode = (TreeNode) e.Data.GetData( typeof( TreeNode ) );

                if ( !( (NodeData) _dropSelection.Tag ).IsDirectory )
                {
                    Debug.Assert( ( (NodeData) _dropSelection.Parent.Tag ).IsDirectory );

                    if ( _dropSelection.Parent == draggedNode.Parent )
                    {
                        _dropSelection = null;
                        e.Effect = DragDropEffects.None;
                        return;
                    }

                    _dropSelection = _dropSelection.Parent;
                }

                if ( _dropSelection == draggedNode )
                {
                    _dropSelection = null;
                    e.Effect = DragDropEffects.None;
                    return;
                }

                TreeNode oldParent = draggedNode.Parent;
                String oldName = draggedNode.FullPath;
                draggedNode.Remove();
                _dropSelection.Nodes.Add( draggedNode );

                String newName = draggedNode.FullPath;

                try
                {
                    bool targetIsDirectory = ( (NodeData) _dropSelection.Tag ).IsDirectory;
                    bool sourceIsDirectory = ( (NodeData) draggedNode.Tag ).IsDirectory;

                    if ( TryRenameResource != null )
                    {
                        TryRenameResourceEventArgs args =
                            new TryRenameResourceEventArgs( oldName, newName, targetIsDirectory );

                        TryRenameResource( this, args );

                        if ( args.Cancel )
                        {
                            throw new ResourceManagerException( "Action cancelled by user..." );
                        }
                    }

                    if ( sourceIsDirectory )
                    {
                        _currentlyRenamedDirectory = new String[] { oldName, newName };
                        ResourceManager.RenameResourceDirectory( oldName, newName );
                    }
                    else
                    {
                        _currentlyRenamedResource = new String[] { oldName, newName };
                        ResourceManager.RenameResource( oldName, newName );
                    }

                    _dropSelection.Expand();
                    _treeView.SelectedNode = draggedNode;
                }
                catch ( ResourceManagerException ex )
                {
                    _showStatusMessage( ex.Message );
                    draggedNode.Remove();
                    oldParent.Nodes.Add( draggedNode );
                }
                finally
                {
                    _dropSelection = null;
                    _currentlyRenamedResource = null;
                    _currentlyRenamedDirectory = null;
                }
            }
        }

        void _treeView_DragOver( object sender, DragEventArgs e )
        {
            if ( !e.Data.GetDataPresent( typeof( TreeNode ) ) )
            {
                e.Effect = DragDropEffects.None;
                return;
            }

            TreeNode draggedItem = (TreeNode) e.Data.GetData( typeof( TreeNode ) );

            if ( draggedItem.TreeView != _treeView )
            {
                e.Effect = DragDropEffects.None;
                return;
            }

            _dropSelection = null;
            Point p = _treeView.PointToClient( new Point( 0, e.Y ) );

            for ( int x = 2; x < _treeView.ClientSize.Width; x += 5 )
            {
                p.X = x;
                _dropSelection = _treeView.GetNodeAt( p );

                if ( _dropSelection != null )
                {
                    break;
                }
            }

            if ( _dropSelection == null )
            {
                e.Effect = DragDropEffects.None;
                _dropSplitterPanel.Visible = false;
                return;
            }

            _updateScrolling( p );

            if ( ( (NodeData) _dropSelection.Tag ).IsDirectory )
            {
                _treeView.SelectedNode = _dropSelection;
                _dropSplitterPanel.Visible = false;
                _dropSelection.Expand();
                e.Effect = DragDropEffects.Move;
                return;
            }

            int half = _dropSelection.Bounds.Top + _dropSelection.Bounds.Height / 2;

            Point location = new Point( _dropSelection.Bounds.Left, 0 );

            if ( e.Y < half )
            {
                location.Y = _dropSelection.Bounds.Top - 1;
            }
            else
            {
                location.Y = _dropSelection.Bounds.Bottom + 1;
            }

            _dropSplitterPanel.Location = location;
            _dropSplitterPanel.Size = new Size( _dropSelection.Bounds.Width, 2 );

            _dropSplitterPanel.Visible = true;
            e.Effect = DragDropEffects.Move;
        }

        void _updateScrolling( Point pt )
        {
            /* all this is to handle the drag up and down beyond boundaries */
            TreeNode node = _treeView.GetNodeAt( pt );
            TimeSpan ts = new TimeSpan( DateTime.Now.Ticks - _scrollTick );

            //scroll up
            if ( pt.Y < _treeView.ItemHeight )
            {
                // if within one node of top, scroll quickly
                if ( node.PrevVisibleNode != null )
                {
                    node = node.PrevVisibleNode;
                }

                node.EnsureVisible();
                _scrollTick = DateTime.Now.Ticks;
            }
            else if ( pt.Y < ( _treeView.ItemHeight * 2 ) )
            {
                // if within two nodes of the top, scroll slowly
                if ( ts.TotalMilliseconds > 250 )
                {
                    node = node.PrevVisibleNode;
                    if ( node.PrevVisibleNode != null )
                    {
                        node = node.PrevVisibleNode;
                    }

                    node.EnsureVisible();
                    _scrollTick = DateTime.Now.Ticks;
                }
            }

            //scroll down
            if ( pt.Y > _treeView.ItemHeight )
            {
                // if within one node of top, scroll quickly
                if ( node.NextVisibleNode != null )
                {
                    node = node.NextVisibleNode;
                }

                node.EnsureVisible();
                _scrollTick = DateTime.Now.Ticks;
            }
            else if ( pt.Y > ( _treeView.ItemHeight * 2 ) )
            {
                // if within two nodes of the top, scroll slowly
                if ( ts.TotalMilliseconds > 250 )
                {
                    node = node.NextVisibleNode;
                    if ( node.NextVisibleNode != null )
                    {
                        node = node.NextVisibleNode;
                    }

                    node.EnsureVisible();
                    _scrollTick = DateTime.Now.Ticks;
                }
            }
        }
        #endregion

        #region Private Helper Methods
        void _showStatusMessage( String message )
        {
            if ( _statusMessageConsumer != null )
            {
                _statusMessageConsumer.StatusMessage = message;
            }
        }

        private String _getImageKey( String extension )
        {
            if ( !_treeViewImageList.Images.ContainsKey( extension ) )
            {
                Icon icon = IconLoader.GetIconForFileName( "*." + extension );

                if ( icon.Size.IsEmpty )
                {
                    icon.Dispose();
                    return DEFAULT_RESOURCE_IMAGE_KEY;
                }

                _treeViewImageList.Images.Add( extension, icon );
            }

            return extension;
        }

        void _createRoot()
        {
            _treeView.Nodes.Clear();

            _rootNode = _treeView.Nodes.Add( _rootDirectory, _rootDirectory, 0, 1 );
            _rootNode.Tag = new NodeData( true );
            _rootNode.ContextMenuStrip = _directoryContextMenu;
        }

        void _loadResources( String extension )
        {
            Debug.Assert( DesignMode || ResourceManager.ResourceDirectoryExists( _rootDirectory ) );

            foreach ( String filename in
                ResourceManager.FindResources( _rootDirectory, extension ) )
            {
                _addResourceToTreeview( _rootDirectory + '\\' + filename );
            }
        }

        void _addResourceToTreeview( String resourceName )
        {
            resourceName = _resourceNameToRelativeName( resourceName );

            String[] parts = resourceName.Split( new char[] { Path.DirectorySeparatorChar } );

            if ( parts.Any( x => _ignoredDirectories.Contains( x ) ) )
            {
                return;
            }

            TreeNode node = _rootNode;

            int i = 0;

            if ( parts[0].Length == 0 )
            {
                ++i;
            }

            for ( ; i < parts.Length - 1; ++i )
            {
                if ( !node.Nodes.ContainsKey( parts[i] ) )
                {
                    TreeNode newNode = new TreeNode();
                    newNode.Text = parts[i];
                    newNode.Name = newNode.Text;
                    newNode.ImageKey = FOLDER_CLOSED_IMAGE_KEY;
                    newNode.SelectedImageKey = FOLDER_OPENED_IMAGE_KEY;
                    newNode.Tag = new NodeData( true );
                    newNode.ContextMenuStrip = _directoryContextMenu;

                    node.Nodes.Add( newNode );
                }

                node = node.Nodes[parts[i]];
            }

            if ( !node.Nodes.ContainsKey( parts.Last() ) )
            {
                String imageKey = _getImageKey( Path.GetExtension( resourceName ) );

                TreeNode newNode = new TreeNode();
                newNode.Text = parts.Last();
                newNode.Name = newNode.Text;
                newNode.ImageKey = imageKey;
                newNode.SelectedImageKey = imageKey;
                newNode.Tag = new NodeData( false );
                newNode.ContextMenuStrip = _resourceContextMenu;

                node.Nodes.Add( newNode );
            }
        }

        String _resourceNameToRelativeName( String resourceName )
        {
            Debug.Assert( _rootDirectory[0] != Path.DirectorySeparatorChar );
            Debug.Assert( _rootDirectory[0] != Path.AltDirectorySeparatorChar );

            StringBuilder builder = new StringBuilder();
            builder.Append( ResourceManager.NormalizeName( resourceName ) );

            if ( builder[0] == Path.DirectorySeparatorChar ||
                 builder[0] == Path.AltDirectorySeparatorChar )
            {
                builder.Remove( 0, 1 );
            }

            builder.Remove( 0, _rootDirectory.Length );
            
            if ( builder[0] == Path.DirectorySeparatorChar ||
                 builder[0] == Path.AltDirectorySeparatorChar )
            {
                builder.Remove( 0, 1 );
            }

            return builder.ToString();
        }

        void _loadDirectories()
        {
            Debug.Assert( DesignMode || ResourceManager.ResourceDirectoryExists( _rootDirectory ) );

            foreach ( String relativeName in
                ResourceManager.FindDirectories( _rootDirectory ) )
            {
                String resourceName = _rootDirectory + '\\' + relativeName;
                _addDirectoryToTreeView( resourceName );
            }
        }

        void _addDirectoryToTreeView( String resourceName )
        {
            String name = _resourceNameToRelativeName( resourceName );

            String[] parts = name.Split( new char[] { Path.DirectorySeparatorChar } );

            if ( parts.Length == 0 )
                return;

            TreeNode node = _rootNode;

            int i = 0;

            if ( parts[0].Length == 0 )
            {
                i = 1;
            }

            for ( ; i < parts.Length; ++i )
            {
                if ( _ignoredDirectories.Contains( parts[i] ) )
                    break;

                if ( !node.Nodes.ContainsKey( parts[i] ) )
                {
                    TreeNode newNode = new TreeNode();
                    newNode.Text = parts[i];
                    newNode.Name = parts[i];
                    newNode.ImageKey = FOLDER_CLOSED_IMAGE_KEY;
                    newNode.SelectedImageKey = FOLDER_OPENED_IMAGE_KEY;
                    newNode.Tag = new NodeData( true );
                    newNode.ContextMenuStrip = _directoryContextMenu;

                    node.Nodes.Add( newNode );
                }

                node = node.Nodes[parts[i]];
            }
        }

        bool _selectedNodeIsDirectory()
        { 
            Debug.Assert( _treeView.SelectedNode != null );
            return ( (NodeData) _treeView.SelectedNode.Tag ).IsDirectory;
        }

        IEnumerable<String> _getResourceExtensions()
        {
            IEnumerable<String> extensions = _resourceExtensions.Split(
                new char[] { ' ', ',', ';' }
                ).Select( x => x.Trim() );
            return extensions;
        }

        TreeNode _getTreeNodeByResourceName( String resourceName )
        {
            String name = _resourceNameToRelativeName( resourceName );
            String[] parts = name.Split( new char[] { Path.DirectorySeparatorChar } );

            if ( parts.Any( x => _ignoredDirectories.Contains( x ) ) )
            {
                return null;
            }

            TreeNode node = _rootNode;

            int i = 0;

            if ( parts[0].Length == 0 )
            {
                ++i;
            }

            for ( ; i < parts.Length - 1; ++i )
            {
                if ( !node.Nodes.ContainsKey( parts[i] ) )
                {
                    return null;
                }

                node = node.Nodes[parts[i]];
            }

            if ( node.Nodes.ContainsKey( parts.Last() ) )
            {
                return node.Nodes[parts.Last()];
            }
            else
            {
                return null;
            }
        }

        void _removeTreeNodeByResourceName( String resourceName )
        {
            TreeNode node = _getTreeNodeByResourceName( resourceName );

            if ( node != null )
            {
                node.Remove();
            }
        }

        void _subscribeToResourceManagerEvents()
        {
            ResourceManager.ResourceCreated += _resourceManager_ResourceCreated;
            ResourceManager.ResourceDeleted += _resourceManager_ResourceDeleted;
            ResourceManager.ResourceRenamed += _resourceManager_ResourceRenamed;
            ResourceManager.ResourceDirectoryCreated += _resourceManager_ResourceDirectoryCreated;
            ResourceManager.ResourceDirectoryDeleted += _resourceManager_ResourceDirectoryDeleted;
            ResourceManager.ResourceDirectoryRenamed += _resourceManager_ResourceDirectoryRenamed;
        }

        void _resourceManager_ResourceDirectoryRenamed( object sender, ResourceRenamedEventArgs e )
        {
            if ( !e.NewName.StartsWith( _rootDirectory ) ||
                 !e.OldName.StartsWith( _rootDirectory ) )
                return;

            if ( _currentlyRenamedDirectory == null ||
                 !( _currentlyRenamedDirectory[0] == e.OldName &&
                    _currentlyRenamedDirectory[1] == e.NewName ) )
            {
                TreeNode oldNode = _getTreeNodeByResourceName( e.OldName );

                if ( oldNode != null )
                {
                    oldNode.Remove();
                }

                _addDirectoryToTreeView( e.NewName );
            }
        }

        void _resourceManager_ResourceRenamed( object sender, ResourceRenamedEventArgs e )
        {
            if ( !e.NewName.StartsWith( _rootDirectory ) || 
                 !e.OldName.StartsWith( _rootDirectory ) )
                return;

            if ( _currentlyRenamedResource == null || 
                 !( _currentlyRenamedResource[0] == e.OldName && 
                    _currentlyRenamedResource[1] == e.NewName ) )
            {
                TreeNode oldNode = _getTreeNodeByResourceName( e.OldName );

                if ( oldNode != null )
                {
                    oldNode.Remove();
                }

                _addResourceToTreeview( e.NewName );
            }
        }

        void _resourceManager_ResourceDirectoryCreated( object sender, ResourceCreatedEventArgs e )
        {
            if ( e.Name.StartsWith( _rootDirectory ) )
            {
                if ( !( _editingNewDirectoryNode && _newDirectoryNodeName == e.Name ) )
                {
                    _addDirectoryToTreeView( e.Name );
                }
            }
        }

        void _unsubscribeFromResourceManagerEvents()
        {
            ResourceManager.ResourceCreated -= _resourceManager_ResourceCreated;
            ResourceManager.ResourceDeleted -= _resourceManager_ResourceDeleted;
            ResourceManager.ResourceRenamed -= _resourceManager_ResourceRenamed;
            ResourceManager.ResourceDirectoryCreated -= _resourceManager_ResourceDirectoryCreated;
            ResourceManager.ResourceDirectoryDeleted -= _resourceManager_ResourceDirectoryDeleted;
            ResourceManager.ResourceDirectoryRenamed -= _resourceManager_ResourceDirectoryRenamed;
        }

        void _resourceManager_ResourceCreated( object sender, ResourceCreatedEventArgs e )
        {
            if ( e.Name.StartsWith( _rootDirectory ) )
            {
                if ( !( _editingNewFileNode && _newFileNodeName == e.Name ) )
                {
                    _addResourceToTreeview( e.Name );
                }
            }
        }

        void _resourceManager_ResourceDeleted( object sender, ResourceDeletedEventArgs e )
        {
            _removeTreeNodeByResourceName( e.Name );
        }

        void _resourceManager_ResourceDirectoryDeleted( object sender, ResourceDeletedEventArgs e )
        {
            _removeTreeNodeByResourceName( e.Name );
        }

        #endregion

        void _treeView_AfterSelect( object sender, TreeViewEventArgs e )
        {
            if ( this.SelectionChanged != null )
            {
                var arg = new ResourceNameEventArgs( this.SelectedResourceName );
                this.SelectionChanged( this, arg );
            }
        }

    }

    public class ResourceNameEventArgs : EventArgs
    {
        public ResourceNameEventArgs( String resourceName )
        {
            ResourceName = resourceName;
        }

        public readonly String ResourceName;
    }

    public class TryRenameResourceEventArgs : EventArgs
    {
        public TryRenameResourceEventArgs( String oldResourceName,
            String newResourceName, bool isDirectory )
        {
            OldResourceName = oldResourceName;
            NewResourceName = newResourceName;
            IsDirectory = isDirectory;
            Cancel = false;
        }

        public readonly String OldResourceName;
        public readonly String NewResourceName;
        public readonly bool IsDirectory;
        public bool Cancel;
    }
}
